Same-Site, Same-Origin について実際に動かしながら挙動を調べてみた
西田@CX事業本部です
今回は、名前も目的も似ていて混同されやすい Same Site と Same Origin について実際に動かしながら挙動を確認してみました
何に使われるもの?
Same Origin
主に同一オリジンポリシーで使われます。同一オリジンポリシーとは、HTMLに埋め込まれたJavaScriptが、その提供元以外のリソースに対してのアクセスを制限するものです
この制限は CSRF 等の攻撃のリスクを軽減することを目的にしています
Same Site
Set-Cookieヘッダの属性の一つで、Cookieの送信先を制限するために使われます。 Set-Cookieヘッダの他の属性である domain や path と同じように設定できます
Set-Cookie: name=value; Domain=<example.jp>; Secure; HttpOnly; SameSite=Strict
Same Site も 主に同一オリジンポリシー と同じく CSRF のリスクの軽減を目的にしています
Same Origin の条件
以下の全てが一致すれば Same Origin となります。1つでも異なれば Cross Origin になります
- スキーマ(http, https)
- ホスト(example.jp, example1.jp)
- ポート(80, 443)
Origin A | Origin B | 判定 | 説明 |
---|---|---|---|
http://example.jp | http://example.jp | same-origin | |
http://example.jp | https://example.jp | cross-origin | スキーマが異なる |
https://example.jp | https://www.example.jp | cross-origin | ホストが異なる |
https://example.jp:3000 | https://example.jp:8888 | cross-origin | ポートが異なる |
Same Site の条件
以下の全てが一致すれば Same Site となります。1つでも異なれば Cross Site になります
- スキーマ(http, https)
- eTLD + 1 (例: ${yourdomain}.co.jp, ${yourdomain}.github.io)
eTLD(effective Top Level Domain) とは、 .com や .org など Root Zone Database に掲載されてるTLD とそのすぐ左側を合わせたものです
.co.jp
や .github.io
など、実質的にTLDとして扱われてるものであり、これらは Public Suffix List にまとめられています
※ Same Origin と違って port の違いは許容されますが、スキーマ(http, https)が異なると実質的には port も異なることになります(http: 80, https: 443)
Site A | Site B | 判定 | 説明 |
---|---|---|---|
https://www.example.jp | https://login.example.jp | same-site | サブドメインが異なっても eTLD + 1 (example.jp) が同じ |
http://example.jp | https://example.jp | cross-site | スキーマが異なる |
Same Origin/Site の違い
Same Origin/Site の違いをまとめてみます。主にサブドメインが same であると判定されるかどうかに違いがあります
Cross Origin は Cross Site よりも厳しい制約となっており、Cross Site なら Cross Origin になりますが、Cross Origin なら必ずしも Cross Site であるとは限りません
A | B | 判定 | 説明 |
---|---|---|---|
https://sub1.example.jp | https://sub2.example.jp | cross-origin, same-site | eTLD + 1 が同じだが、ホスト名が異なる |
https://example.com | https://example1.com | cross-origin, cross-site | eTLD + 1が異なる、ホスト名も異なる |
Cross Origin の時の Cookie 送信
Cross Origin の場合は CORS 関連のヘッダを設定し、 fetch 関数であれば credentials オプションに include を設定しないと Cookie は送信されません
Cross Site の場合はさらに SameSite 属性に None
を設定する必要があります
SameSiteに設定する値
Same Site に設定できる値は Strict, Lax(デフォルト), None の3種類あります。 Cross Site リクエスト時に Strict だと Cookie は送信されません。None であれば Cookie が送信されます
デフォルトである Lax だとトップレベルナビゲーションの時のみ Cookie を送信します。トップレベルナビゲーションとは、WEBブラウザのURLバーのURLと、表示されてるページが一致する場合です。具体的な例だと、Cross Site のサイトから <img>
タグで参照されている場合は Cookie は送信されず、 <a>
タグでその画像のURLに遷移すると Cookie が送信されます
試してみた
簡単なサーバーを用意して、Cross Origin、Cross Siteの動きを実際に検証していきます
全体構成
- app1.example.jp から example1.jpへのリクエストが Cross Origin, Cross Site になります
- app1.example.jp から app2.example.jp へのリクエストが Cross Origin, Same Site になります
また、 app1.example.jp には、以下の Set-Cookie で Cookie が設定されています
Set-Cookie: test=1; SameSite=Lax
検証
app1.example.jp から他のサイトへいろんな条件下でリクエストすることで挙動を確認していきます
JavaScript で fetch を使って Cross Origin と Same Origin のサイトにアクセスする関数を用意します
// Cross Origin リクエスト function fetchFromOther() { fetch("https://app2.example.jp") } // Same Origin リクエスト function fetchFromOrigin() { fetch("/") }
HTMLに以下の要素を記述します
- 上記の関数を呼び出すハンドラーが設定された button 要素
- Cross Site, Same Site 上の画像を参照する img 要素
- img 要素の src に対しリンクで移動する a 要素
<button onclick="fetchFromOther()">fetch Other</button> <button onclick="fetchFromOrigin()">fetch Origin</button> <a href="https://{{.OtherHost}}/hanako.jpeg">Other</a> <image src="https://{{.OtherHost}}/hanako.jpeg" /> <a href="/hanako.jpeg">Origin</a> <image src="/hanako.jpeg" />
Cross Origin に対する JavaScript からのアクセス
app1.example.jp から app2.example.jp への fetch は Cross Origin になり、CORS エラーになり、リクエストが送信されませんでした
Access to fetch at 'https://xxxx/' from origin 'https://xxx' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
CORSエラーを解消し、リクエストを成功させるには、Cross Origin のレスポンスに、リクエストの元の Origin を許可するCORS関連のHTTPヘッダーを含める必要があります
Access-Control-Allow-Origin: app1.example.jp
Same Site に対する img タグの Cookie 送信
app1.example.jp に埋め込まれた app2.example.jp の画像の Cookie は送信されました
Cross Site に対する img タグの Cookie 送信
app1.example.jp に埋め込まれた example1.jp の画像の Cookie が送信されませんでした
Same Site に Cookie を送信するには、SameSite属性に None を指定する必要があり、その際に Secure 属性も設定する必要があります
Set-Cookie: test=1; SameSite=None; Secure
Cross Site に対する a タグでの画面遷移時の Cookie 送信
app1.example.jp から example1.jp の画像へのリンクでの遷移では Cookie が送信されました
但し、Cookie の SameSite 属性に Strict を設定した場合は aタグでの遷移でも Cookie が送信されませでした
Set-Cookie: test=1; SameSite=Strict
Cross Origin かつ Cross Site に対する Cookie の送信について
Cross Origin でCookieを送信するために fetch の credentials オプションに include を指定します
function fetchFromOther() { fetch("https://app2.example.jp", { credentials: "include" }) }
さらに、Cross Origin のレスポンスヘッダに Access-Control-Allow-Origin
にリクエスト元のオリジンを指定しリクエストを許可する必要があります。また、 Access-Control-Allow-Credentials
に true を指定する必要があります。 Access-Control-Allow-Credentials
にtrueを指定した場合に Access-Control-Allow-Origin
に *
アスタリスクは使えない点にご注意ください
Access-Control-Allow-Origin: https://app1.example.jp
そして、Cross Site にCookieを送信したい場合は、 Cookie の SameSite 属性に None
と Secure 属性を指定する必要があります
Set-Cookie: test=1; SameSite=None; Secure
まとめ
今回はなんとく設定していた Cross Origin と Cross Siteの違いを確認しながら、実際に手を動かして挙動を理解してみました。この記事が誰かの役に立てば嬉しいです